<?php
/*
 This file is part of beContent.
 Foobar is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 Foobar is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with beContent.  If not, see <http://www.gnu.org/licenses/>.
 http://www.becontent.org
 */
DEFINE("SQUARE", 1);
DEFINE("CURL", 2);
DEFINE('DEBUG', "DEBUG");
DEFINE("DEFAULT_TAGLIB", 'becontentTags');
class Template
{
	var $template_file, $buffer, $foreach, $content, $pars, $parsed, $foreach_counter, $cache, $tags, $escaped_tags, $javascript;
	/**
	 * Constructor for Template Class.
	 * @return
	 * @param object $filename[optional]
	 */
	function Template($filename = "")
	{
		if (count($GLOBALS['config']['languages']) > 0 && !file_exists($filename))
		{
			$filename = substr($filename, 0, strpos($filename, "dtml"))."dtml/".$GLOBALS['config']['currentlanguage'].substr($filename, strpos($filename, "dtml")+4);
		}
		$this->foreach = new ForeachCode();
		$this->template_file = basename($filename);
		$this->cache = new Cache($this->template_file);
		$this->parsed = FALSE;
		$this->foreach_counter = 1;
		$this->setTagStyle(SQUARE);
		if (file_exists($filename) && ! empty($filename))
		{
			$fp = fopen($filename, "r");
			$this->buffer = fread($fp, filesize($filename));
			fclose($fp);
		}
		else
		{
			$this->buffer = $filename;
		}
	}
	/**
	 * Checks if the placeholders within template file are used inside and outside foreach statements or in different foreaches
	 * using the same name.
	 * Trigger error is generated if it occurs.
	 * @return null;
	 */
	function checkPlaceholders($buffer)
	{
		$placeholders = array ();
		$temp_buffer = $buffer;
		while (preg_match("~<\[foreach\]>((?:(?!<\[/?foreach\]>).)*)<\[\/foreach\]>~us", $temp_buffer, $foreachCode))
		{
			$backupPH = $placeholders;
			preg_match_all("~{$this->escaped_tags['open']}(?!foreach|\/foreach)(.+?)(::(.*))?{$this->escaped_tags['close']}~us", $foreachCode[1], $placeholderstemp, PREG_PATTERN_ORDER);
			foreach ($placeholderstemp[1] as $ph)
			{
				if (in_array($ph, $backupPH))
				{
					trigger_error("cannot define variables with the same name in  different foreach constructs : ({$this->tags['open']}$ph{$this->tags['close']})", E_USER_ERROR);
				}
				else
				{
					$placeholders[] = $ph;
				}
				$temp_buffer = preg_replace("~<\[foreach\]>(?:(?!<\[/?foreach\]>).)*<\[\/foreach\]>~us", '', $temp_buffer, 1);
			}
		}
		preg_match_all("~{$this->escaped_tags['open']}(?!foreach|\/foreach)(.+?)(::(.*))?{$this->escaped_tags['close']}~us", $temp_buffer, $rootplaceholders, PREG_PATTERN_ORDER);
		foreach ($rootplaceholders as $ph)
		{
			if (in_array($ph, $placeholders))
			{
				trigger_error("cannot define variables with the same name in foreach construct and out of them : ({$this->tags['open']}$ph{$this->tags['close']})", E_USER_ERROR);
			}
		}
		return null;
	}
	/**
	 * Returns parsed values for $content array.
	 * @return object $finalContent
	 * @param object $content
	 */
	function loadContent($content)
	{
		$contentKeys = $content[0];
		$contentValue = $content[1];
		$finalContent = array ();
		$numKeys = count($contentKeys);
		for ($i = 0; $i < $numKeys; $i++)
		{
			$placeholderName = $contentKeys[$i];
			$placeholderValue = $contentValue[$i];
			if ($foreachName = $this->foreach ->getForeachName($placeholderName)) // Se è un contenuto iterato
			{
				$foreachCode = $this->foreach ->getForeachCode($foreachName);
				$parsedContent = $this->transformContent($contentKeys[$i], $contentValue[$i], $foreachCode);
			}
			else // Se non è un contenuto iterato
			{
				$parsedContent = $this->transformContent($contentKeys[$i], $contentValue[$i], $this->buffer);
			}
			if (is_array($parsedContent))
			{
				foreach ($parsedContent as $currentParsedContentName=>$currentParsedContentValue)
				{
					$finalContent[0][] = $currentParsedContentName;
					$finalContent[1][] = $currentParsedContentValue;
				}
			}
		}
		return $finalContent;
	}
	/**
	 * Transforms content and returns values array
	 * @return array
	 * @param object $name
	 * @param object $data
	 * @param object $buffer
	 */
	function transformContent($name, $data, $buffer)
	{
		$value = null;
		$library_obj = null;
		$tokensA = array ();
		$tokensB = array ();
		$paramTokens = array ();
		$parameters = array ();
		$simple_pattern = '~<\[('.$name.')\]>~Us';
		$complex_pattern = '~<\[(('.$name.')::(\w+)?)\]>~Us';
		$complex_pattern_param = '~<\[(('.$name.')::(\w+)\s+([^\]]+))\]>~Us';
		preg_match_all($complex_pattern_param, $buffer, $tokensA, PREG_SET_ORDER);
		preg_match_all($complex_pattern, $buffer, $tokensB, PREG_SET_ORDER);
		$tokens = array_merge($tokensA, $tokensB);
		foreach ((array)$tokens as $token)
		{
			unset ($library);
			$selector = $token[3];
			if (defined("DEFAULT_TAGLIB"))
			{
				$library = DEFAULT_TAGLIB;
			}
			$result = preg_match_all('~(\w+)=\"?([^\"]+)\"?~s', $token[4], $paramTokens, PREG_SET_ORDER);
			foreach ((array)$paramTokens as $paramToken)
			{
				if (strcmp($paramToken[1], 'library') == 0)
				{
					if (!file_exists("include/tags/{$paramToken[2]}.inc.php"))
					{
						trigger_error("Library <b>{$paramToken[2]}</b> does not exists!", E_USER_ERROR);
					}
					else
					{
						$library = $paramToken[2];
					}
				}
				else
				{
					$parameters[$paramToken[1]] = $paramToken[2];
				}
			}
			include_once ("include/tags/$library.inc.php");
			eval ("\$library_obj = new \$library();");
			if (strcmp($selector, '') == 0)
			{
				trigger_error("undefined selector of tagLibrary for <i><[$name]></i> variable", E_USER_ERROR);
			}
			else if (strcmp($selector, 'library') == 0)
			{
				trigger_error("cannot use string <i>library</i> as a selector for <i><[$name]></i> variable", E_USER_ERROR);
			}
			else if (!(method_exists($library_obj, $selector)))
			{
				trigger_error("selector <b>$selector</b> does not exist in library <b>$library</b>", E_USER_ERROR);
			}
			$value[$token[1]] = $library_obj->apply($name, $data, $parameters, $selector);
		}
		$result = preg_match_all($simple_pattern, $buffer, $tokens, PREG_SET_ORDER);
		foreach ((array)$tokens as $token)
		{
			$value[$token[1]] = $data;
		}
		return $value;
	}
	/**
	 * Launches a default set of setContent methods.
	 * Returns TRUE on success.
	 * @return boole
	 */
	function setDefaultContents()
	{
		$this->setContent("server", ( isset ($_SERVER['SERVER_NAME']))?$_SERVER['SERVER_NAME']:'');
		$this->setContent("skin", ( isset ($GLOBALS['config']['skin']))?$GLOBALS['config']['skin']:'');
		$this->setContent("base", ( isset ($GLOBALS['config']['base']))?$GLOBALS['config']['base']:'');
		$this->setContent("user.username", ( isset ($_SESSION['user']['username']))?$_SESSION['user']['username']:'');
		$this->setContent("user.name", ( isset ($_SESSION['user']['name']))?$_SESSION['user']['name']:'');
		$this->setContent("user.surname", ( isset ($_SESSION['user']['surname']))?$_SESSION['user']['surname']:'');
		$this->setContent("user.lastlogin", ( isset ($_SESSION['user']['lastlogin']))?$_SESSION['user']['lastlogin']:'');
		$this->setContent("email", ( isset ($_SESSION['user']['email']))?$_SESSION['user']['email']:'');
	}
	/**
	 * Parses template file.
	 * @return null;
	 */
	function parse()
	{
		if ($this->template_file == "frame-public.html")
		{
			if ( isset ($_REQUEST['mode']) && ((strcmp($_REQUEST['mode'], 'ajax') == 0) or (strcmp($_REQUEST['mode'], 'compact') == 0)))
			{
				list ($index) = array_keys($this->content[0], 'body');
				$this->buffer = $this->content[1][$index];
			}
			else
			{
				$this->common();
			}
		}
		if ($this->cache->caching && $this->cache->cacheFileExists() && $this->cache->checkCacheFile())
		{
			$this->buffer = $this->cache->getCacheFileContent();
		}
		else
		{
			$this->setDefaultContents();
			$this->checkPlaceholders($this->buffer);
			$this->buffer = $this->foreach ->parseForeach($this->buffer);
			$this->content = $this->loadContent($this->content);
			$this->buffer = $this->foreach ->bindAll($this->content, $this->buffer);
			$this->buffer = $this->loadEmptyContent($this->buffer);
		}
		if (($this->cache->caching && !$this->cache->cacheFileExists()) || ($this->cache->caching && $this->cache->cacheFileExists() && !$this->cache->checkCacheFile()))
		{
			$this->buffer = $this->cache->buildCacheFile($this->buffer, $this->template_file);
		}
		$this->parsed = true;
		return null;
	}
	/**
	 * Creates extension code for includeJS,includeStyle,injectJS and injectStyle methods in tag library classes.
	 * @return string
	 */
	function getExtension()
	{
		$extension = "\n<!-- being generated inclusions -->\n";
		$extensions = array ();
		foreach (get_declared_classes() as $k=>$v)
		{
			if (strcmp(strtolower(get_parent_class($v)), 'taglibrary') == 0)
			{
				if (method_exists($v, version_compare(phpversion(), "5.0", "<")?'includejs':'includeJS'))
				{
					eval ("\$extensions['includeJS'][] = ".$v."::includeJS();");
				}
				if (method_exists($v, version_compare(phpversion(), "5.0", "<")?'includestyle':'includeStyle'))
				{
					eval ("\$extensions['includeStyle'][] = ".$v."::includeStyle();");
				}
				if (method_exists($v, version_compare(phpversion(), "5.0", "<")?'injectjs':'injectJS'))
				{
					eval ("\$extensions['injectJS'][]= ".$v."::injectJS();");
				}
				if (method_exists($v, version_compare(phpversion(), "5.0", "<")?'injectstyle':'injectStyle'))
				{
					eval ("\$extensions['injectStyle'][] = ".$v."::injectStyle();");
				}
			}
		}
		foreach ((array)$extensions['includeJS'] as $file)
		{
			$extension .= "<script type=\"text/javascript\" src=\"{$file}\"></script>\n";
		}
		foreach ((array)$extensions['includeStyle'] as $file)
		{
			$extension .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$file}\" />\n";
		}
		foreach ((array)$extensions['injectJS'] as $code)
		{
			$extension .= "<script>\n{$code}\n\n</script>\n";
		}
		foreach ((array)$extensions['injectStyle'] as $code)
		{
			$extension .= "<style type=\"text/css\">\n{$code}\n\n</style>\n";
		}
		$extension .= "\n<!-- end generated inclusions -->\n";
		return $extension;
	}
	/**
	 * Echoes current $buffer.
	 * @return
	 * @param object $buffer
	 */
	function display($buffer)
	{
		echo $buffer;
	}
	/**
	 * Sets directly template buffer with a content
	 * @return null
	 * @param object $content
	 */
	function setTemplate($content)
	{
		$this->buffer = $content;
		return null;
	}
	/**
	 * Sets a beEngine Tag Style (CURL/SQUARE).
	 * @return
	 * @param object $style
	 */
	function setTagStyle($style)
	{
		switch($style)
		{
			case CURL:
				$this->tags['open'] = "{";
				$this->tags['close'] = "}";
				$this->escaped_tags['open'] = "{";
				$this->escaped_tags['close'] = "}";
				break;
			case SQUARE:
			default:
				$this->tags['open'] = "<[";
				$this->tags['close'] = "]>";
				$this->escaped_tags['open'] = "<\[";
				$this->escaped_tags['close'] = "\]>";
				break;
		}
		return null;
	}
	/**
	 * Sets content for a placeholder
	 * @return
	 * @param object $name
	 * @param object $value
	 * @param object $pars[optional]
	 */
	function setContent($name, $value, $pars = "")
	{
		if (gettype($name) != "string")
		{
			trigger_error('$name cannot be a '.gettype($name).' must be a string', E_USER_WARNING);
		}
		$this->content[0][] = $name;
		$this->content[1][] = $value;
		$this->content[2] = $pars;
		return null;
	}
	/**
	 * Sets content for a placeholder only if it has not been already set.
	 * @return
	 * @param object $name
	 * @param object $value
	 * @param object $pars[optional]
	 */
	function setContentOnce($name, $value, $pars = "")
	{
		if (!$array_search($name, $this->content[0]))
		{
			$this->setContent($name, $value, $pars);
		}
		return null;
	}
	/**
	 * Concatenates $pars to main content and $value to an already existing placeHolder.
	 * @return
	 * @param object $name
	 * @param object $value
	 * @param object $pars[optional]
	 */
	function addentContent($name, $value, $pars = "")
	{
		if (gettype($name) != "string")
		{
			trigger_error('$name cannot be a '.gettype($name).' must be a string', E_USER_WARNING);
		}
		if ($key = $array_search($name, $this->content[0]))
		{
			$this->content[1][$key] .= $value;
			$this->content[2] .= $pars;
		}
	}
	/**
	 * Launches some common functions for each 'main' template
	 * @return
	 */
	function common()
	{
		if (function_exists("shared"))
		{
			shared();
		}
	}
	/**
	 * Returns main buffer with parsed and sanitized content.
	 * @return string
	 */
	function get()
	{
		if (!$this->parsed)
		{
			$this->parse();
		}
		$removePatterns = array ('~<\[(?!foreach\d+_\d+|\/foreach\d+_\d+).+\]>~Us', '~<\[foreach\d+_\d+\]>~Us', '~<\[\/foreach\d+_\d+\]>~Us');
		$this->buffer = preg_replace($removePatterns, '', $this->buffer);
		return $this->buffer;
	}
	/**
	 * Appends extension codes into <head> tag and displays sanitized parsed output
	 * @return
	 */
	function close()
	{
		$this->setContent("style", $this->getExtension());
		if (!$this->parsed)
		{
			$this->parse();
		}
		$this->buffer = preg_replace('~<\[(?!foreach\d+_\d+|\/foreach\d+_\d+).+\]>~Us', '', $this->buffer, -1);
		$this->buffer = preg_replace('~<\/head>~Us', $this->getExtension().'</head>', $this->buffer, 1);
		$this->display($this->buffer);
		return null;
	}
	/**
	 * Sets cache lifetime forwarding $lifetime parameter to setCacheLifetime cache method.
	 * @return
	 * @param object $lifetime
	 */
	function setCacheLifeTime($lifetime)
	{
		$this->cache->setCacheLifetime($lifetime);
	}
	/**
	 * Enable/disable caching passing boolean $flag.
	 * @return
	 * @param boolean $flag
	 */
	function caching($flag)
	{
		$this->cache->caching($flag);
	}
	/**
	 * Loads both empty content and tag library calls for unset placeholders
	 * @return
	 * @param object $buffer
	 */
	function loadEmptyContent($buffer)
	{
		$finalContent = NULL;
		$patterns = array ();
		$replacements = array ();
		preg_match_all("~<\[(?!foreach\d+_\d+|\/foreach\d+_\d+)(.+)\]>~Us", $buffer, $token, PREG_SET_ORDER);
		foreach ((array)$token as $placeholder)
		{
			if (preg_match("~([^:]+)~", $placeholder[1], $token2))
			{
				$placeholderName = $token2[1];
				$parsedContent = $this->transformContent($placeholderName, NULL, $buffer);
				if (is_array($parsedContent))
				{
					foreach ((array)$parsedContent as $currentParsedContentName=>$currentParsedContentValue)
					{
						$patterns[] = "~{$this->escaped_tags['open']}".$currentParsedContentName."{$this->escaped_tags['close']}~Us";
						$replacements[] = $currentParsedContentValue;
					}
				}
			}
		}
		$buffer = preg_replace($patterns, $replacements, $buffer);
		return $buffer;
	}
}
class ForeachCode
{
	/**
	 * $key => foreachName, $value => foreachCode;
	 * @var
	 */
	var $foreachCodeArray;
	/** $key => foreachName, $value => array($key => index, $value=> childForeachName)
	 * @var
	 */
	var $foreachHierarchyArray;
	/**
	 * $key => foreachName , $value => array($key=> placeHolderName,$value => true/false)
	 * $this->placeHolderBooleanMap[$foreachName][$placeholder] = true;
	 * @var
	 */
	var $placeHolderBooleanMap;
	/**
	 * $key => foreachName, $val => rootForeachID
	 * @var
	 */
	var $placeHolderForeachOwnerMap;
	/**
	 * $key => placeHolderName, $val => rootForeachName
	 * @var
	 */
	var $foreachParentMap;
	/** 
	 * $key => placeHolderName, $val => foreachName
	 * @var
	 */
	/**
	 * Null Constructor
	 * @return 
	 */
	function ForeachCode()
	{
	}
	/**
	 * Enumerates foreach statements adding deepness and ID number, It also checks syntax and fills parent foreach map.
	 * @return string
	 * @param string $buffer
	 */
	function getForeachEnumeration($buffer)
	{
		$deep = -1;
		$ID = 0;
		$IDarray[0] = 1;
		$lastRootForeachID = 0;
		$counter_foreach_opened = 0;
		$counter_foreach_closed = 0;
		while ($result = preg_match("~<\[\/?foreach\]>~Us", $buffer, $foreachOpenTag))
		{
			if ($foreachOpenTag[0][2] === 'f')
			{
				$counter_foreach_opened++;
				$deep++;
				$IDarray[] = ++$ID;
				$buffer = preg_replace("~<\[foreach\]>~Us", "<[foreach".$deep."_".$ID."]>", $buffer, 1);
				$this->foreachParentMap["foreach".$deep."_".$ID] = ($deep == 1)?$lastRootForeachID:0;
				if ($deep == 0)
				{
					$lastRootForeachID = $ID;
				}
			}
			else if ($foreachOpenTag[0][2] === '/')
			{
				$counter_foreach_closed++;
				$lastForeachID = array_pop($IDarray);
				$buffer = preg_replace("~<\[\/foreach\]>~Us", "<[/foreach".$deep."_".$lastForeachID."]>", $buffer, 1);
				$deep--;
			}
		}
		if ($counter_foreach_opened > $counter_foreach_closed)
		{
			trigger_error("cannot define <i><[foreach]></i> open tag without <i><[/foreach]></i> close tag", E_USER_ERROR);
		}
		else if ($counter_foreach_opened < $counter_foreach_closed)
		{
			trigger_error("cannot define <i><[/foreach]></i> close tag without <i><[foreach]></i> open tag", E_USER_ERROR);
		}
		return $buffer;
	}
	/**
	 * Launches enumeration and fillForeachArray for every Foreach statement found.
	 * @return
	 * @param object $buffer
	 */
	function parseForeach($buffer)
	{
		$buffer = $this->getForeachEnumeration($buffer);
		preg_match_all("~<\[(foreach(\d+)_(\d+))\]>(.+)<\[\/foreach\\2_\\3\]>~Us", $buffer, $token, PREG_SET_ORDER);
		foreach ((array)$token as $currentForeach)
		{
			$this->fillForeachArray($currentForeach[1], $currentForeach[4]);
			$buffer = preg_replace("~<\[$currentForeach[1]\]>.+<\[\/$currentForeach[1]\]>~Us", "<[$currentForeach[1]]><!--FOREACH CODE--><[/$currentForeach[1]]>", $buffer, -1);
		}
		return $buffer;
	}
	/**
	 * Cleans nested foreach code and fills foreachArray and foreachHierarchyArray structures.
	 * @return
	 * @param object $foreachName
	 * @param object $foreachCode
	 */
	function fillForeachArray($foreachName, $foreachCode)
	{
		$foreachCleanedCode = preg_replace("~<\[(foreach(\d+)_(\d+))\]>.+<\[\/foreach\\2_\\3\]>~Us", "<[foreach\\2_\\3]>FOREACH_CODE<[/foreach\\2_\\3]>", $foreachCode);
		$this->foreachCodeArray[$foreachName] = $foreachCleanedCode;
		preg_match_all("~<\[(?!foreach\d+_\d+|\/foreach\d+_\d+)(.+)\]>~Us", $foreachCleanedCode, $token, PREG_SET_ORDER);
		foreach ((array)$token as $currentPlaceholder)
		{
			$this->setForeachPlaceholder($foreachName, $currentPlaceholder[1]);
		}
		preg_match_all("~<\[(foreach(\d+)_(\d+))\]>(.+)<\[\/foreach\\2_\\3\]>~Us", $foreachCode, $token, PREG_SET_ORDER);
		foreach ((array)$token as $currentForeachChild)
		{
			$this->foreachHierarchyArray[$foreachName][] = $currentForeachChild[1];
			$this->fillForeachArray($currentForeachChild[1], $currentForeachChild[4]);
		}
	}
	/**
	 * Loop within content array and bind Foreach statement values recursively.
	 * @return
	 * @param object $foreachName
	 * @param object $foreachCode
	 * @param object $foreachPlaceholders
	 * @param object $foreachChilds
	 * @param object $content
	 */
	function bindForeach($foreachName, $foreachCode, $foreachPlaceholders, $foreachChilds, & $content)
	{
		reset($content);
		while ($currentContentName = current($content[0]))
		{
			$currentContentValue = current($content[1]);
			if (!$this->isForeachPlaceholder($currentContentName))
			{
				$currentContentName = next($content[0]);
				$currentContentValue = next($content[1]);
			}
			else
			{
				if ($this->isTherePlaceholder($currentContentName, $foreachName))
				{
					$foreachCode = $this->setPlaceholderValue($currentContentName, $currentContentValue, $foreachName, $foreachCode);
					$currentContentName = next($content[0]);
					$currentContentValue = next($content[1]);
				}
				else
				{
					if ($this->isRelativeForeachPlaceholder($currentContentName, $foreachName))
					{
						if (! empty($foreachChilds) && $this->isDescendantForeachPlaceholder($currentContentName, $foreachName))
						{
							$child = $this->getChildFromDescendantPlaceholder($currentContentName, $foreachName);
							$foreachCode = $this->setForeachBindedCode($child, $foreachCode, $this->bindForeach($child, $this->getForeachCode($child), $this->getForeachPlaceholders($child), $this->getForeachChilds($child), $content));
						}
						else
						{
							return $foreachCode;
						}
					}
					else
					{
						$currentContentName = next($content[0]);
						$currentContentValue = next($content[1]);
					}
				}
			}
		}
		return $foreachCode;
	}
	/**
	 * Binds content for both not within-foreach and within-foreach placeholders.
	 * @return
	 * @param object $content
	 * @param object $buffer
	 */
	function bindAll($content, $buffer)
	{
		if ( empty($content))
		{
			return $buffer;
		}
		list ($contentKeys, $contentValue) = $content;
		$count = count($contentValue);
		for ($i = 0; $i < $count; $i++)
		{
			$buffer = $this->setSimplePlaceholderValue($contentKeys[$i], $contentValue[$i], $buffer);
		}
		preg_match_all("~<\[(foreach\d+_\d+)\]>.+<\[\/\\1\]>~Us", $buffer, $token, PREG_SET_ORDER);
		foreach ((array)$token as $currentForeachName)
		{
			$temp_content = $content;
			$currentForeachCode = $this->getForeachCode($currentForeachName[1]);
			$currentForeachChilds = $this->getForeachChilds($currentForeachName[1]);
			$currentForeachPlaceholders = $this->getForeachPlaceholders($currentForeachName[1]);
			$buffer = $this->setForeachBindedCode($currentForeachName[1], $buffer, $this->bindForeach($currentForeachName[1], $currentForeachCode, $currentForeachPlaceholders, $currentForeachChilds, $temp_content));
		}
		return $buffer;
	}
	/**
	 * Returns deepness of a foreach statement from its foreachName.
	 * Assumption : beContent at the moment handles 1-deep nested foreach statements.
	 * @return integer
	 * @param	 string $foreachName
	 */
	function getForeachDeep($foreachName)
	{
		return $foreachName[7];
	}
	/**
	 * Returns ID of a foreach statement from its foreachName.
	 * @return integer
	 * @param	 string $foreachName
	 */
	function getForeachID($foreachName)
	{
		return substr(strrchr($foreachName, '_'), 1);
	}
	/**
	 * Returns an array of foreach names which are $foreachName childs.
	 * @return array
	 * @param object $foreachName
	 */
	function getForeachChilds($foreachName)
	{
		if (!$this->foreachHierarchyArray)
		{
			return null;
		}
		else
		{
			return ($this->foreachHierarchyArray[$foreachName]?$this->foreachHierarchyArray[$foreachName]:null);
		}
	}
	/**
	 * Returns an array of placeholders names within $foreachName foreach.
	 * @return array
	 * @param object $foreachName
	 */
	function getForeachPlaceholders($foreachName)
	{
		return array_keys($this->placeHolderBooleanMap[$foreachName]);
	}
	/**
	 * Returns  $foreachName internal code.
	 * @return string
	 * @param object $foreachName
	 */
	function getForeachCode($foreachName)
	{
		return ($this->foreachCodeArray[$foreachName])?$this->foreachCodeArray[$foreachName]:null;
	}
	/**
	 * Returns $foreachName name container for a placeholder, null if it is not inside a foreach statement
	 * @return string
	 * @param object $placeholderName
	 */
	function getForeachName($placeholderName)
	{
		return $this->placeHolderForeachOwnerMap[$placeholderName];
	}
	/**
	 * Returns a $foreachName which represents $foreachName's father
	 * @return
	 * @param object $foreachName
	 */
	function getRootForeach($foreachName)
	{
		return ($this->getForeachDeep($foreachName) == 0)?$foreachName:'foreach0_'.$this->foreachParentMap[$foreachName];
	}
	/**
	 * Checks whether $placeholderName is a placeholder within a generic foreach statement.
	 * @return bool
	 * @param object $placeholderName
	 */
	function isForeachPlaceholder($placeholderName)
	{
		foreach ($this->placeHolderBooleanMap as $currentForeachName=>$currentPlaceholderSet)
		{
			if ($this->placeHolderBooleanMap[$currentForeachName][$placeholderName])
			{
				return true;
			}
		}
		return false;
	}
	/**
	 * Checks whether $placeholderName is located in $foreachName foreach or not.
	 * @return bool
	 * @param object $placeholderName
	 * @param object $foreachName
	 */
	function isTherePlaceholder($placeholderName, $foreachName)
	{
		return $this->placeHolderBooleanMap[$foreachName][$placeholderName];
	}
	/**
	 * Checks whether $placeholderName is relative to a specific foreach statement ( considering discendant foreachs )
	 * @return
	 * @param object $placeholderName
	 * @param object $foreachName
	 */
	function isRelativeForeachPlaceholder($placeholderName, $foreachName)
	{
		$rootForeach = $this->getRootForeach($foreachName);
		
		foreach ($this->getForeachPlaceholders($rootForeach) as $rootPlaceholderName)
		{
			if (strcmp($rootPlaceholderName, $placeholderName) == 0)
			{
				return TRUE;
			}
		}
		if ($this->isDescendantForeachPlaceholder($placeholderName, $rootForeach))
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	/**
	 * Retrieve all foreach childs for a foreach and checks wheter $placeholderName is within $foreachName or
	 * in one of his childs.
	 * @return boolean
	 * @param object $placeholderName
	 * @param object $foreachName
	 */
	function isDescendantForeachPlaceholder($placeholderName, $foreachName)
	{
		$foreachChilds = $this->getForeachChilds($foreachName);
		foreach ((array)$foreachChilds as $child)
		{
			if ($this->isTherePlaceholder($placeholderName, $child) || $this->isDescendantForeachPlaceholder($placeholderName, $child))
			{
				return TRUE;
			}
		}
		return FALSE;
	}
	/**
	 * Retrieve all foreach childs for a foreach and returns child foreach if $placeholderName is within $foreachName or
	 * in one of his childs.
	 * @return string
	 * @param object $placeholderName
	 * @param object $foreachName
	 */
	function getChildFromDescendantPlaceholder($placeholderName, $foreachName)
	{
		$foreachChilds = $this->getForeachChilds($foreachName);
		foreach ((array)$foreachChilds as $child)
		{
			if ($this->isTherePlaceholder($placeholderName, $child) || $this->isDescendantForeachPlaceholder($placeholderName, $child))
			{
				return $child;
			}
		}
		return null;
	}
	/**
	 * Sets value for a specific $placeholderName.
	 * @return
	 * @param object $placeholderName
	 * @param object $placeholderValue
	 * @param object $foreachName
	 * @param object $foreachCode
	 */
	function setPlaceholderValue($placeholderName, $placeholderValue, $foreachName, $foreachCode)
	{
		$foreachCode = preg_replace("~<\[$placeholderName\]>~Us", $placeholderValue, $foreachCode, 1, $replacements);
		if ($replacements > 0)
		{
			return $foreachCode;
		}
		else
		{
			return preg_replace("~<\[(foreach\d+_\d+)\]>.+<\[\/\\1\]>~Us", "", $foreachCode).preg_replace("~<\[$placeholderName\]>~Us", $placeholderValue, $this->getForeachCode($foreachName));
		}
	}
	/**
	 * Returns buffer with parsed code for $placeholderName;
	 * @return
	 * @param object $placeholderName
	 * @param object $placeholderValue
	 * @param object $buffer
	 */
	function setSimplePlaceholderValue($placeholderName, $placeholderValue, $buffer)
	{
		return preg_replace("~<\[$placeholderName\]>~Us", "".$placeholderValue, $buffer, -1);
	}
	/**
	 * Returns buffer with parsed code for $foreachName
	 * @return string;
	 * @param object $foreachName
	 * @param object $buffer
	 * @param object $code
	 */
	function setForeachBindedCode($foreachName, $buffer, $code)
	{
		return preg_replace("~<\[$foreachName\]>.+<\[\/$foreachName\]>~Us", $code, $buffer, 1);
	}
	/**
	 * Set placeHolder in placeHolderBooleanMap
	 * @return
	 * @param object $foreachName
	 * @param object $placeholder
	 */
	function setForeachPlaceholder($foreachName, $placeholder)
	{
		$this->placeHolderBooleanMap[$foreachName][$placeholder] = true;
		list ($ph) = explode("::", $placeholder);
		$this->placeHolderForeachOwnerMap[$ph] = $foreachName;
	}
}
class Skin extends Template
{
	var $name, $templates, $placeholders, $private, $cache, $cached, $cache_name, $timeout;
	
	/**
	 * Constructor for Skin class
	 * @return null
	 * @param object $skin[optional]
	 */
	function Skin($skin = "")
	{
		$GLOBALS['config']['skin'] = (!empty($skin))?$skin:$GLOBALS['config']['skin'];
		$GLOBALS["current_skin"] = $GLOBALS['config']['skin'];
	
		$this->name = $GLOBALS["current_skin"];
		
		if (class_exists("Auth"))
		{
			Template::Template("skins/{$this->name}/dtml/frame-private.html");
			$this->private = true;
		}
		else
		{
			Template::Template("skins/{$this->name}/dtml/frame-public.html");
			$this->private = false;
		}
		
		$this->cache_name = "{$GLOBALS['config']['cache_folder']}/".md5(basename($_SERVER['SCRIPT_FILENAME'])."?{$_SERVER['QUERY_STRING']}-{$GLOBALS['config']['currentlanguage']}-{$GLOBALS['config']['currenttab']}").".html";
		if ($this->regenerateCache())
		{
			$this->setContent("skin", $skin);
			$this->setContent("base", $GLOBALS['config']['base']);
			$this->setContent("server", $_SERVER['SERVER_NAME']);
		}
		else
		{
			$cache = new Template($this->cache_name);
			$cache->close();
			exit ();
		}
	}
	
	/**
	 * Checks whether or not regenerate a cache file for template
	 * @return bool
	 */
	function regenerateCache()
	{
		if (basename($_SERVER['SCRIPT_FILENAME']) == "error.php")
		{
			return true;
		}
		if ( isset ($_REQUEST['nocache']))
		{
			return true;
		}
		if ($GLOBALS['config']['cache_mode'] == NONE)
		{
			return true;
		}
		if ($this->private )
		{
			return true;
		}
		if (!(file_exists($this->cache_name)) or (filemtime($this->cache_name)+$GLOBALS['config']['cache_timeout'] < time()))
		{
			return true;
		}
		return false;
	}
	
	
	function addSkin($placeholder, $template)
	{
		$this->templates[$template] = & new Template("skins/{$this->name}/dtml/{$template}.html");
		$this->placeholders[$template] = $placeholder;
	}
	function setSkinContent($template, $name, $value)
	{
		#echo get_class($this->templates[$template]) $this->templates[$template]->setContent($name, $value);
	}
	function close()
	{
		if (is_array($this->templates))
		{
			foreach ($this->templates as $name=>$template)
			{
				$this->setContent($this->placeholders[$name], $template->get());
			}
		}
		if ($this->private )
		{
			$this->addentContent("javascript", "<script language=\"javascript\" type=\"text/javascript\" src=\"js/tiny_mce/tiny_mce.js\"></script>\n");
			if ($GLOBALS['config']['jquery'])
			{
				$this->addentContent("javascript", "<link type=\"text/css\" rel=\"stylesheet\" href=\"http://ui.jquery.com/testing/themes/base/ui.all.css\" />\n");
				$this->addentContent("javascript", "<script type=\"text/javascript\" src=\"js/jquery.js\"></script>\n");
				$this->addentContent("javascript", "<script type=\"text/javascript\" src=\"js/jquery-ui-personalized-1.5.3.min.js\"></script>\n");
				#$this->addentContent("javascript", "<script type=\"text/javascript\" src=\"js/jquery-ui-personalized-1.5.3.js\"></script>\n");
			}
			else
			{
				$this->addentContent("javascript", "<script language=\"javascript\" type=\"text/javascript\" src=\"js/becontent.js\"></script>\n");
			}
			$this->addentContent("head", "<link rel=\"stylesheet\" type=\"text/css\" media=\"screen, print\" href=\"css/beContent.css\" />");
		}
		Template::close();
		if (!$this->private )
		{
			$buffer = $this->buffer;
			$fp = fopen($this->cache_name, "w");
			fwrite($fp, $buffer);
			fclose($fp);
		}
	}
}
class Skinlet extends Template
{
	function Skinlet($template)
	{
		if (!strpos($template, "."))
		{
			Template::Template("skins/{$GLOBALS['current_skin']}/dtml/{$template}.html");
		}
		else
		{
			Template::Template("skins/{$GLOBALS['current_skin']}/dtml/{$template}");
		}
	}
}
class Cache
{
	var $caching, $cache_dir, $cache_file, $cache_file_lifetime;
	function Cache($template_file)
	{
		$this->cache_dir = $this->setDefaultCacheDir();
		$this->caching = FALSE;
		$this->cache_file_lifetime = 60;
		$this->template_file_name = $template_file;
		$this->cache_file = $this->setDefaultCacheFile();
	}
	function setDefaultCacheFile()
	{
		$cache_dir = "cache";
		$cache_file = $cache_dir."/".$this->template_file_name."_temp.php";
		return $cache_file;
	}
	function setDefaultCacheDir()
	{
		$cache_dir = "cache";
		return $cache_dir;
	}
	function caching($flag)
	{
		if (gettype($flag) == "boolean")$this->caching = $flag;
		else
			trigger_error('$flag cannot be a '.gettype($flag).' must be a boolean', E_USER_WARNING);
		if (!$flag)
		{
			if ($this->cacheFileExists())
			{
				if (PHP_OS == "WINNT" or PHP_OS == "WIN32")
				{
					$temp_dir = $this->getDefaultCacheDirName();
				$temp_file = $this->getDefaultCacheFileName();
			exec("del $temp_dir\\$temp_file", $output, $result);
		if ($result != 0)
		{
			trigger_error("cannot remove <i>$this->cache_file</i> cache file", E_USER_WARNING);
	}
	}
	else
	{
		exec("del $this->cache_file", $output, $result);
	if ($result != 0)
	{
		trigger_error("cannot remove <i>$this->cache_file</i> cache file", E_USER_WARNING);
	}
	}
	exec("rmdir $this->cache_dir", $output, $result);
	if ($result != 0)
	{
		trigger_error("cannot remove <i>$this->cache_dir</i> directory", E_USER_WARNING);
	}
	}
	}
	else
	{
		exec("mkdir $this->cache_dir", $output, $result);
	if ($result != 0 and !$this->cacheFileExists())
	{
		trigger_error("cannot create <i>$this->cache_dir</i> directory", E_USER_WARNING);
	}
	}
	}
function buildCacheFile($buffer, $template_file)
{
	$cfile = fopen($this->cache_file, "w");
$info_cache_file = fstat($cfile);
$buffer = "<!-- This page has been generated the first time on ".date("d/m/Y", $info_cache_file[10])." at ".date("H:i:s", $info_cache_file[10]).". Last generation on ".date("d/m/Y", $info_cache_file[9])." at ".date("H:i:s", $info_cache_file[9])." -->".$buffer;
fwrite($cfile, $buffer);
fclose($cfile);
return $buffer;
}
function getCacheFileContent()
{
	$cfile = fopen($this->cache_file, "r");
$buffer = fread($cfile, filesize($this->cache_file));
fclose($cfile);
return $buffer;
}
function cacheFileExists()
{
	return file_exists($this->cache_file);
}
function checkCacheFile()
{
	$cfile = fopen($this->cache_file, "r");
$info_cache_file = fstat($cfile);
$lifetime = (time())-($info_cache_file[9]);
if ($this->cache_file_lifetime < $lifetime)
{
	fclose($cfile);
return FALSE;
}
else
{
	fclose($cfile);
return TRUE;
}
}
function setCacheLifetime($lifetime)
{
	if (gettype($lifetime) == "integer")$this->cache_file_lifetime = $lifetime;
else
	trigger_error('$lifetime cannot be a '.gettype($lifetime).' must be an integer', E_USER_WARNING);
}
function setCacheDir($dir)
{
	if (gettype($dir) == "string")$this->cache_dir = $dir;
else
	trigger_error('$dir cannot be a '.gettype($dir).' must be a string', E_USER_WARNING);
}
function getDefaultCacheDirName()
{
	return $this->cache_dir;
}
function getDefaultCacheFileName()
{
	$result = preg_match("~\/(.+)~", $this->cache_file, $token);
return $token[1];
}
}
class TagLibrary
{
	function apply($name, $data, $pars, $selector)
	{
		$result = call_user_func( array ($this, $selector), $name, $data, $pars);
	return $result;
}
}
